package com.stone.serverless.web;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.stone.serverless.web.domain.RouterInfo;
import com.stone.serverless.web.domain.ServerlessClassRequestMappingInfo;
import com.stone.serverless.web.domain.ServerlessMethodRequestMappingInfo;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @Description: TODO
 * @Author: MECHREVO
 * @Date: 2022/1/17 18:53
 * @Version: 1.0.0
 */
@Component
public class StartWeb {

    private ServerlessWebContext context;

    private JettyServer jettyServer;

    /**
     * 控制器bean
     */
    private Map<Object, List<RequestMappingInfo>> routerInfoMap = new HashMap<>();

    private Map<String, Map<String, List<Object>>> componentNameMap = new HashMap<>();

    public Map<String, Map<String, List<Object>>> getComponentNameMap() {
        return componentNameMap;
    }

    public void start(Map<String, Object> environment, String commonScan) {
        ServerlessWebContext context = new ServerlessWebContext();

        context.setClassLoader(this.getClass().getClassLoader());
        context.scan(commonScan);
        MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
        PropertySource<?> systemProperties = propertySources.get("systemProperties");
        if (systemProperties instanceof PropertiesPropertySource) {
            PropertiesPropertySource s = (PropertiesPropertySource) systemProperties;

            String[] propertyNames = s.getPropertyNames();

            Properties properties = new Properties();
            for (String str : propertyNames) {
                if (str.equals("spring.liveBeansView.mbeanDomain")) {
                    continue;
                }
                Object source = s.getProperty(str);
                properties.put(str, source);

            }
            PropertiesPropertySource p = new PropertiesPropertySource("systemProperties", properties);
            propertySources.remove("systemProperties");
            propertySources.addAfter("servletContextInitParams", p);
            //Map<String, Object> source = s.getSource();
            //Properties pp =  convert(source);
            //pp.put("spring.liveBeansView.mbeanDomain", null);
        }
        propertySources.addFirst(new MapPropertySource("default", environment));


        jettyServer = new JettyServer();
        jettyServer.run(context);
        context.setServletContext(jettyServer.servletContext);
        context.refresh();
        jettyServer.start();
        this.context = context;
    }


    public void stop() {
        this.context.stop();
        this.context.close();
        this.jettyServer.stop();
    }

    public void registerRouters(RouterInfo routerInfo) {
        if (ObjectUtil.isNull(routerInfo) || CollUtil.isEmpty(routerInfo.getRequestMappingInfo())) {
            return;
        }
        //找到webServer
        RequestMappingHandlerMapping rmhm = (RequestMappingHandlerMapping) this.context.getBean("requestMappingHandlerMapping");
        if (rmhm == null) {
            return;
        }
        String componentName = routerInfo.getComponentName();
        if (null == componentName || "".equals(componentName)) {
            System.out.println("组件名称不能为空");
            return;
        }
        String version = routerInfo.getVersion();
        List<ServerlessClassRequestMappingInfo> requestMappingInfo = routerInfo.getRequestMappingInfo();
        for (ServerlessClassRequestMappingInfo serverlessClassRequestMappingInfo : requestMappingInfo) {
            Object getHandler = serverlessClassRequestMappingInfo.getHandler();

            //如果已经存在直接下一个
            if (this.routerInfoMap != null && this.routerInfoMap.containsKey(getHandler)) {
                continue;
            }
            Map<String, List<Object>> objectMap = componentNameMap.getOrDefault(componentName, new HashMap<>());

            List<Object> list = objectMap.getOrDefault(version, new ArrayList<>());
            list.add(getHandler);
            objectMap.put(version, list);
            componentNameMap.put(componentName, objectMap);

            List<ServerlessMethodRequestMappingInfo> methodRequestMappingInfoList = serverlessClassRequestMappingInfo.getMethodRequestMappingInfoList();
            try {
                Map<Method, RequestMappingInfo> methods = new HashMap<>();
                for (ServerlessMethodRequestMappingInfo serverlessMethodRequestMappingInfo : methodRequestMappingInfoList) {
                    //resolveEmbeddedValuesInPatterns(String[] patterns)
                    RequestMappingInfo mappingForMethod = this.getMappingForMethod(componentName, version, serverlessMethodRequestMappingInfo, serverlessClassRequestMappingInfo, rmhm);
                    methods.put(ReflectUtil.invoke(serverlessMethodRequestMappingInfo, "getMethod"), mappingForMethod);
                    //resolveEmbeddedValuesInPatterns
                }

                methods.forEach((method, mapping) -> {
                    try {
                        rmhm.registerMapping(mapping, getHandler, method);
                        List<RequestMappingInfo> orDefault = this.routerInfoMap.getOrDefault(getHandler, new ArrayList<>());
                        orDefault.add(mapping);
                        this.routerInfoMap.put(getHandler, orDefault);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    protected RequestMappingInfo getMappingForMethod(String componentName, String version, ServerlessMethodRequestMappingInfo method,
                                                     ServerlessClassRequestMappingInfo serverlessClassRequestMappingInfo, RequestMappingHandlerMapping handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method, handlerType);
        if (info != null) {
            RequestMappingInfo typeInfo = createRequestMappingInfo(serverlessClassRequestMappingInfo, handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
            String prefix = componentName + "/" + version;//getPathPrefix(ReflectUtil.invoke(serverlessClassRequestMappingInfo, "getHandler").getClass(), handlerType);
            if (prefix != null) {
                RequestMappingInfo.BuilderConfiguration re = (RequestMappingInfo.BuilderConfiguration) ReflectUtil.getFieldValue(handlerType, "config");

                info = RequestMappingInfo.paths(prefix).options(re).build().combine(info);
            }
        }
        return info;
    }

    protected RequestMappingInfo createRequestMappingInfo(ServerlessClassRequestMappingInfo requestMapping, RequestMappingHandlerMapping requestMappingHandlerMapping) {
        String[] paths = requestMapping.getPaths();
        if (null == paths || paths.length <= 0) {
            return null;
        }

        List<String> methods = requestMapping.getMethods();
        RequestMethod[] requestMethods = null;
        if (CollUtil.isNotEmpty(methods)) {
            requestMethods = methods.stream().map(RequestMethod::valueOf).toArray(RequestMethod[]::new);
        } else {
            requestMethods = new RequestMethod[0];
        }

        Method resolveEmbeddedValuesInPatterns = ReflectUtil.getMethodByName(requestMappingHandlerMapping.getClass(), "resolveEmbeddedValuesInPatterns");

        RequestMappingInfo.BuilderConfiguration re = (RequestMappingInfo.BuilderConfiguration) ReflectUtil.getFieldValue(requestMappingHandlerMapping, "config");
        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(ReflectUtil.invoke(requestMappingHandlerMapping, resolveEmbeddedValuesInPatterns, paths))
                .methods(requestMethods)
                .params(requestMapping.getParams())
                .headers(requestMapping.getHeaders())
                .consumes(requestMapping.getConsumes())
                .produces(requestMapping.getProduces())
                .mappingName(requestMapping.getMappingName());
        return builder.options(re).build();
    }

    public void uninstallRouters(String componentName, String version) {
        if (ObjectUtil.isNull(componentName) || ObjectUtil.isNull(version)) {
            return;
        }
        Map<String, List<Object>> objectMap = componentNameMap.get(componentName);
        if (MapUtil.isEmpty(objectMap)) {
            return;
        }
        List<Object> objects = objectMap.get(version);

        if (CollUtil.isEmpty(objects)) {
            return;
        }
        for (Object v : objects) {
            List<RequestMappingInfo> requestMappingInfos = routerInfoMap.get(v);
            RequestMappingHandlerMapping rmhm = (RequestMappingHandlerMapping) this.context.getBean("requestMappingHandlerMapping");
            requestMappingInfos.forEach(rmhm::unregisterMapping);
            routerInfoMap.remove(v);

            Map<String, List<Object>> cm = componentNameMap.get(componentName);
            if (MapUtil.isNotEmpty(cm)) {
                cm.remove(version);
                if (MapUtil.isEmpty(cm)) {
                    componentNameMap.remove(componentName);
                }
            }
        }
    }

    public List<String> routerStatistics(String componentName, String version) {
        List<String> res = new ArrayList<>();
        Map<String, List<Object>> objectMap = componentNameMap.get(componentName);
        if (MapUtil.isEmpty(objectMap)) {
            return Collections.emptyList();
        }
        List<Object> objects = objectMap.get(version);

        for (Object v : objects) {
            List<RequestMappingInfo> requestMappingInfos = routerInfoMap.get(v);
            requestMappingInfos.forEach(handler -> {
                res.add(handler.toString());
            });
        }
        return res;
    }
}
